import { CronJob, CronTime } from 'cron'
import child_process from 'child_process'
import iconv from 'iconv-lite'
import { v4 as uuidv4 } from 'uuid'
import treeKill from 'tree-kill';
import cronConfig from './cron-config'
import logUtil from './log-util'

const logger = logUtil.getLogger('cron-manage')

const cronManage = {
  job: null,
  config: null,
  processCache: {},
  history: {
    send: function(data) {
    },
    _hendle: function(data) {
      this.send(data)
      logger.info('新历史记录\n' + JSON.stringify(data, null, 2))
    },
    info: function(message) {
      this._hendle({
        type: 'info',
        message: message,
        time: new Date(),
      })
    },
    _jobEvent: function(type, message, job) {
      this._hendle({
        type: type,
        message: message,
        time: new Date(),
        job: job,
        config: cronManage.config
      })
    },
    jobStart: function(job) {
      this._jobEvent('job_start', '任务执行中', job)
    },
    jobFinish: function(job) {
      if (job.success) {
        this._jobEvent('job_success', '任务已完成', job)
      } else if (job.killed) {
        this._jobEvent('job_kill', '任务已被终止', job)
      } else {
        this._jobEvent('job_fail', '任务执行失败', job)
      }
    },
  },

  init: function() {
    // 加载配置
    this.reset(cronConfig.load())
  },
  reset: function(config) {
    let cronTime = this._cronConvert(config.cronTime)
    // 测试Cron表达式是否可用
    new CronTime(cronTime)

    if (config.enable) {
      if (this.job) {
        this.history.info('任务配置已更新')
        this.job.stop()
      } else {
        this.history.info('调度器已启动')
      }

      this.job = new CronJob(
        cronTime,
        () => this.runOnce(),
        null,
        true
      )
    }

    // 持久化配置
    cronConfig.save(this.config = config)
  },
  enable: function(val) {
    const enableChanged = this.config.enable != val
    if (!enableChanged) {
      return
    }

    // 持久化配置
    this.config.enable = val
    cronConfig.save(this.config)

    if (val) {
      this.reset(this.config)
    } else {
      this.history.info('调度器已停止')
      this.job.stop()
      this.job = null
    }
  },
  _cronConvert: function(cronTime) {
    let textList = cronTime.split(' ')
    if (textList.length !== 6) {
      console.error('cron is invalid: ' + cronValue.value)
      return cronTime
    }

    // 转换月份
    textList[4] = this._cronMonthConvert(textList[4])
    return textList.join(' ')
  },
  _cronMonthConvert: function(text) {
    const incrementRegexp = /^(\d+)\/(\d+)$/
    const specificRegexp = /^\d+(,\d+)*$/
    const rangeRegexp = /^(\d+)-(\d+)$/

    // 任意
    if (text === '*') {
      return text
    }
    // 增量
    let increment = text.match(incrementRegexp)
    if (increment) {
      let start = Number(increment[1]) - 1
      let incr = increment[2]
      return `${start}/${incr}`
    }
    // 切确
    if (specificRegexp.test(text)) {
      return text.split(',')
        .map((m) => Number(m) - 1)
        .join(',')
    }
    // 范围
    let range = text.match(rangeRegexp)
    if (range) {
      let start = Number(range[1]) - 1
      let end = Number(range[2]) - 1
      return `${start}-${end}`
    }
    return text
  },
  runOnce: function() {
    let id = uuidv4()
    let startTime = new Date()
    const options = { encoding: 'buffer' }

    let cmd = this.config.command
    if (cmd.indexOf(' ') >= 0) {
      cmd = `"${cmd}"`
    }
    if (this.config.args) {
      cmd += ' ' + this.config.args
    }
    if (cmd.trim().length === 0) {
      this.history.info('没有执行目标，本次触发已被忽略')
      return
    }

    // 执行命令
    let process = child_process.exec(cmd, options, (err, stdout, stderr) => {
      stdout = iconv.decode(stdout, this.config.encoding)
      stderr = iconv.decode(stderr, this.config.encoding)
      let success = true
      let killed = false
      let exitCode = 0
      
      if (err) {
        success = false
        killed = err.killed
        exitCode = err.code
      }

      // 保存历史记录：任务结束
      this.history.jobFinish({
        id: id,
        pid: process.pid,
        startTime: startTime,
        endTime: new Date(),
        stdout: stdout,
        stderr: stderr,
        success: success,
        killed: killed || process.treeKilled,
        exitCode: exitCode,
      })

      delete this.processCache[id]
    })
    this.processCache[id] = process

    // 保存历史记录：任务开始
    this.history.jobStart({
      id: id,
      pid: process.pid,
      startTime: startTime,
    })
  },
  killJob: function(id) {
    let process = this.processCache[id]
    if (process && process.exitCode === null) {
      treeKill(process.pid, 'SIGKILL')
      process.treeKilled = true
    }
  }
}

export default cronManage
